package k8s_ui

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/wonderivan/logger"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"strconv"
	"time"
)

var Deployment deployment

type deployment struct{}

// 定义DeployCreate结构体，用于创建deployment需要的参数属性的定义
type DeployCreate struct {
	Name          string            `json:"name"`
	Namespace     string            `json:"namespace"`
	Replicas      int32             `json:"replicas"`
	Image         string            `json:"image"`
	Label         map[string]string `json:"label"`
	Cpu           string            `json:"cpu"`
	Memory        string            `json:"memory"`
	ContainerPort int32             `json:"container_port"`
	HealthCheck   bool              `json:"health_check"`
	HealthPath    string            `json:"health_path"`

	//PVC
	Pvc_create   bool   `json:"pvc_create"`
	Pvcdata_name string `json:"pvcdata_name"`
	Pvcdata_url  string `json:"pvcdata_url"`
	Pool         string `json:"pool"`

	//SC
	AccessModes        string  `json:"accessmodes"`
	Storage            string  `json:"storage"`
	Storage_class_name *string `json:"storage_class_name"`

	//nodeport
	Container_Proxy      bool   `json:"container_proxy"`
	Container_Proxy_Mode string `json:"container_proxy_mode"`
	Container_Proxy_Port int32  `json:"container_proxy_port"`
}

// 定义DeploysNp类型，用于返回namespace中deployment的数量
type DeploysNp struct {
	Namespace string `json:"namespace"`
	DeployNum int    `json:"deployment_num"`
}

type DeploymentsResp struct {
	Items []appsv1.Deployment `json:"items"`
	Total int                 `json:"total"`
}

func (p *deployment) GetDeployments(filterName, namespace string, limit, page int) (deploymentsResp *DeploymentsResp, err error) {
	//获取deploymentList类型的deployment列表
	deploymentList, err :=
		K8s.ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		logger.Error(errors.New("获取Deployment列表失败, " + err.Error()))
		return nil, errors.New("获取Deployment列表失败, " + err.Error())
	}
	//将deploymentList中的deployment列表(Items)，放进dataselector对象中，进行排序
	selectableData := &DataSelector{
		GenericDataList: p.toCells(deploymentList.Items),
		DataSelect: &DataSelectQuery{
			Filter: &FilterQuery{Name: filterName},
			Paginate: &PaginateQuery{
				Limit: limit,
				Page:  page,
			},
		},
	}
	filtered := selectableData.Filter()
	total := len(filtered.GenericDataList)
	data := filtered.Sort().Paginate()
	//将[]DataCell类型的deployment列表转为appsv1.deployment列表
	deployments := p.fromCells(data.GenericDataList)
	return &DeploymentsResp{
		Items: deployments,
		Total: total,
	}, nil
}

// 获取deployment详情
func (d *deployment) GetDeploymentDetail(deploymentName string, namespace string) (deployment *appsv1.Deployment, err error) {

	//通过colony 到数据库查询接口绑定名称
	//通过接口调用实例参数
	//k8sSql = &model.Createk8s{}
	//tx := db.GORM.Where("colony = ?", colony).First(&k8sSql)
	//if tx.Error != nil && tx.Error.Error() != "record not found" {
	//	logger.Error("获取K8s_config_token数据k8s_name失败" + tx.Error.Error())
	//	return nil, nil, errors.New("获取K8s_config_token数据k8s_name失败" + tx.Error.Error())
	//}
	////已经取到对应实例接口名称 k8sSql.Colony
	//var colonyname = k8sSql.Colony

	deployment, err =
		K8s.ClientSet.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{})
	if err != nil {
		logger.Error(errors.New("获取Deployment详情失败, " + err.Error()))
		return nil, errors.New("获取Deployment详情失败, " + err.Error())
	}
	return deployment, nil
}

// 设置deployment副本数
func (*deployment) ScaleDeployment(deploymentName, namespace string, scaleNum int) (replicas int32, err error) {
	//获取副本数对象
	scale, err :=
		K8s.ClientSet.AppsV1().Deployments(namespace).GetScale(context.TODO(), deploymentName, metav1.GetOptions{})
	if err != nil {
		logger.Error("获取Deployment副本数失败," + err.Error())
		return 0, errors.New("获取Deployment副本数失败," + err.Error())
	}
	//修改副本数
	scale.Spec.Replicas = int32(scaleNum)
	//更新副本数
	newScale, err := K8s.ClientSet.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), deploymentName, scale, metav1.UpdateOptions{})
	if err != nil {
		logger.Error("更新Deployment副本数失败," + err.Error())
		return 0, errors.New("更新Deployment副本数失败," + err.Error())
	}

	return newScale.Spec.Replicas, nil
}

// 创建deployment,接收DeployCreate对象（k8s默认yaml格式传递）
func (d *deployment) CreateDeployment(data *DeployCreate) (err error) {

	var deployment *appsv1.Deployment
	//判断是否需要存储挂载
	if data.Pvc_create {
		//先创建pvc
		data.Pvcdata_name = data.Name + "-data"
		data.AccessModes = "ReadWriteOnce"
		if data.AccessModes != "" {
			PersistentVolumeClaim.CreatePersistentVolumeClaim(data.Pvcdata_name, data.Namespace, data.AccessModes, data.Storage, data.Storage_class_name)
			if err != nil {
				logger.Error(errors.New("创建PersistentVolumeClaim失败, " + err.Error()))
				return errors.New("创建PersistentVolumeClaim失败, " + err.Error())
			}
		}

		//将data中的数据组装成appsv1.Deployment对象
		deployment = &appsv1.Deployment{
			//ObjectMeta中定义资源名、命名空间以及标签
			ObjectMeta: metav1.ObjectMeta{
				Name:      data.Name,
				Namespace: data.Namespace,
				Labels:    data.Label,
			},
			//Spec中定义副本数、选择器、以及pod属性
			Spec: appsv1.DeploymentSpec{
				Replicas: &data.Replicas,
				Selector: &metav1.LabelSelector{
					MatchLabels: data.Label,
				},
				Template: corev1.PodTemplateSpec{
					//定义pod名和标签
					ObjectMeta: metav1.ObjectMeta{
						Name:   data.Name,
						Labels: data.Label,
					},
					//定义容器名、镜像和端口
					Spec: corev1.PodSpec{
						Containers: []corev1.Container{
							{
								Name:  data.Name,
								Image: data.Image,
								Ports: []corev1.ContainerPort{
									{
										Name:          "http",
										Protocol:      corev1.ProtocolTCP,
										ContainerPort: 80,
									},
								},

								// Existing Name, Image, and Ports definitions...
								VolumeMounts: []corev1.VolumeMount{
									{
										Name:      data.Pvcdata_name,
										MountPath: data.Pvcdata_url,
									},
								},
							},
						},
						Volumes: []corev1.Volume{
							{
								Name: data.Pvcdata_name,
								VolumeSource: corev1.VolumeSource{
									PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
										ClaimName: data.Pvcdata_name,
									},
								},
							},
						},
					},
				},
			},
			//Status定义资源的运行状态，这里由于是新建，传入空的appsv1.DeploymentStatus{}对象即可
			Status: appsv1.DeploymentStatus{},
		}
	} else {
		deployment = &appsv1.Deployment{
			//ObjectMeta中定义资源名、命名空间以及标签
			ObjectMeta: metav1.ObjectMeta{
				Name:      data.Name,
				Namespace: data.Namespace,
				Labels:    data.Label,
			},
			//Spec中定义副本数、选择器、以及pod属性
			Spec: appsv1.DeploymentSpec{
				Replicas: &data.Replicas,
				Selector: &metav1.LabelSelector{
					MatchLabels: data.Label,
				},
				Template: corev1.PodTemplateSpec{
					//定义pod名和标签
					ObjectMeta: metav1.ObjectMeta{
						Name:   data.Name,
						Labels: data.Label,
					},
					//定义容器名、镜像和端口
					Spec: corev1.PodSpec{
						Containers: []corev1.Container{
							{
								Name:  data.Name,
								Image: data.Image,
								Ports: []corev1.ContainerPort{
									{
										Name:          "http",
										Protocol:      corev1.ProtocolTCP,
										ContainerPort: 80,
									},
								},
							},
						},
					},
				},
			},
			//Status定义资源的运行状态，这里由于是新建，传入空的appsv1.DeploymentStatus{}对象即可
			Status: appsv1.DeploymentStatus{},
		}
	}
	//判断是否打开健康检查功能，若打开，则定义ReadinessProbe和LivenessProbe
	if data.HealthCheck {
		//设置第一个容器的ReadinessProbe，因为我们pod中只有一个容器，所以直接使用index 0即可
		//若pod中有多个容器，则这里需要使用for循环去定义了
		deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				HTTPGet: &corev1.HTTPGetAction{
					Path: data.HealthPath,
					//intstr.IntOrString的作用是端口可以定义为整型，也可以定义为字符串
					//Type=0则表示表示该结构体实例内的数据为整型，转json时只使用IntVal的数据
					//Type=1则表示表示该结构体实例内的数据为字符串，转json时只使用StrVal的数据
					Port: intstr.IntOrString{
						Type:   0,
						IntVal: data.ContainerPort,
					},
				},
			},
			//初始化等待时间
			InitialDelaySeconds: 5,
			//超时时间
			TimeoutSeconds: 5,
			//执行间隔
			PeriodSeconds: 5,
		}
		deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{
			ProbeHandler: corev1.ProbeHandler{
				HTTPGet: &corev1.HTTPGetAction{
					Path: data.HealthPath,
					Port: intstr.IntOrString{
						Type:   0,
						IntVal: data.ContainerPort,
					},
				},
			},
			InitialDelaySeconds: 15,
			TimeoutSeconds:      5,
			PeriodSeconds:       5,
		}
		//定义容器的limit和request资源
		deployment.Spec.Template.Spec.Containers[0].Resources.Limits =
			map[corev1.ResourceName]resource.Quantity{
				corev1.ResourceCPU:    resource.MustParse(data.Cpu),
				corev1.ResourceMemory: resource.MustParse(data.Memory),
			}
		deployment.Spec.Template.Spec.Containers[0].Resources.Requests =
			map[corev1.ResourceName]resource.Quantity{
				corev1.ResourceCPU:    resource.MustParse(data.Cpu),
				corev1.ResourceMemory: resource.MustParse(data.Memory),
			}
	}
	//调用sdk创建deployment

	//K8s.ClientSet.AppsV1().Deployments(data.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
	_, err = K8s.ClientSet.AppsV1().Deployments(data.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
	if err != nil {
		logger.Error(errors.New("创建Deployment失败, " + err.Error()))
		return errors.New("创建Deployment失败, " + err.Error())
	}

	// 创建Service资源
	ServiceName := data.Name + "-service"
	if data.Container_Proxy_Mode == "ClusterIP" {
		data.Container_Proxy_Port = 0
	}
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ServiceName,
			Namespace: data.Namespace,
			Labels:    data.Label,
			//Labels: map[string]string{
			//	"app": "nginx",
			//},
		},
		Spec: corev1.ServiceSpec{
			Ports: []corev1.ServicePort{
				{
					Name:       "http",
					Protocol:   corev1.ProtocolTCP,
					Port:       data.ContainerPort,
					TargetPort: intstr.FromInt(int(data.ContainerPort)),
					NodePort:   data.Container_Proxy_Port,
				},
			},
			Selector: data.Label,
			//Selector: map[string]string{
			//	"app": "nginx",
			//},
			Type: corev1.ServiceType(data.Container_Proxy_Mode),
		},
	}

	_, err = K8s.ClientSet.CoreV1().Services(data.Namespace).Create(context.TODO(), service, metav1.CreateOptions{})
	if err != nil {
		logger.Error(errors.New("创建service失败, " + err.Error()))
		return errors.New("创建service失败, " + err.Error())
	}

	//// 创建Ingress资源(升级本地k8s1.20+来测试)
	//IngressName := data.Name + "ingress"
	//IngressUrl := data.Name + ".com"
	//ingress := &v1.Ingress{
	//	ObjectMeta: metav1.ObjectMeta{
	//		Name:      IngressName,
	//		Namespace: data.Namespace,
	//	},
	//	Spec: v1.IngressSpec{
	//		Rules: []v1.IngressRule{
	//			{
	//				Host: IngressUrl,
	//				IngressRuleValue: v1.IngressRuleValue{
	//					HTTP: &v1.HTTPIngressRuleValue{
	//						Paths: []v1.HTTPIngressPath{
	//							{
	//								Path: "/",
	//								PathType: func() *v1.PathType {
	//									pt := v1.PathTypePrefix
	//									return &pt
	//								}(),
	//								Backend: v1.IngressBackend{
	//									Service: &v1.IngressServiceBackend{
	//										Name: IngressName,
	//										Port: v1.ServiceBackendPort{
	//											Number: data.ContainerPort,
	//										},
	//									},
	//								},
	//							},
	//						},
	//					},
	//				},
	//			},
	//		},
	//	},
	//}
	//
	//// 创建Ingress资源
	//_, err = K8s.ClientSet.NetworkingV1().Ingresses(data.Namespace).Create(context.TODO(), ingress, metav1.CreateOptions{})
	//if err != nil {
	//	logger.Error(errors.New("创建ingress失败, " + err.Error()))
	//	return errors.New("创建ingress失败, " + err.Error())
	//}

	return nil
}

// 创建deployment,以前端直接json方式发送创建请求
func (d *deployment) CreateDeployment_test() (err error) {
	// Define the deployment in JSON
	deploymentJSON := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"labels":{"app":"redis"},"name":"redis","namespace":"redis2"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"redis"}},"template":{"metadata":{"labels":{"app":"redis"}},"spec":{"containers":[{"image":"redis:5.0.7","name":"redis","ports":[{"containerPort":6379,"name":"tcp6379","protocol":"TCP"}],"volumeMounts":[{"mountPath":"/tmp","name":"redis-storage"}]}],"volumes":[{"name":"redis-storage","persistentVolumeClaim":{"claimName":"redis-pvc"}}]}}}}`

	// Unmarshal JSON into a Deployment object
	var deployment appsv1.Deployment
	err = json.Unmarshal([]byte(deploymentJSON), &deployment)
	if err != nil {
		panic(err.Error())
	}

	// Create the deployment
	_, err = K8s.ClientSet.AppsV1().Deployments("redis2").Create(context.TODO(), &deployment, metav1.CreateOptions{})
	if err != nil {
		panic(err.Error())
	}
	return err
}

// 删除deployment
func (d *deployment) DeleteDeployment(deploymentName, namespace string) (err error) {
	err = K8s.ClientSet.AppsV1().Deployments(namespace).Delete(context.TODO(),
		deploymentName, metav1.DeleteOptions{})
	if err != nil {
		logger.Error(errors.New("删除Deployment失败, " + err.Error()))
		return errors.New("删除Deployment失败, " + err.Error())
	}
	return nil
}

// 重启deployment
func (*deployment) RestartDeployment(deploymentName, namespace string) (err error) {
	//此功能等同于一下kubectl命令
	//kubectl deployment ${service} -p \
	//'{"spec":{"template":{"spec":{"containers":[{"name":"'"${service}"'","env":
	//[{"name":"RESTART_","value":"'$(date +%s)'"}]}]}}}}'

	patchData := map[string]interface{}{
		"spec": map[string]interface{}{
			"template": map[string]interface{}{
				"spec": map[string]interface{}{
					"containers": []map[string]interface{}{
						{"name": deploymentName,
							"env": []map[string]string{{
								"name":  "RESTART_",
								"value": strconv.FormatInt(time.Now().Unix(), 10),
							}},
						},
					},
				},
			},
		},
	}

	//把patchData序列化成字节
	patchBytes, err := json.Marshal(patchData)
	if err != nil {
		logger.Error("序列化失败," + err.Error())
		return errors.New("序列化失败," + err.Error())
	}
	//更新deployment
	_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Patch(context.TODO(), deploymentName, "application/strategic-merge-patch+json", patchBytes, metav1.PatchOptions{})
	if err != nil {
		logger.Error("重启Deployment失败," + err.Error())
		return errors.New("重启Deployment失败," + err.Error())
	}

	return nil
}

// 更新deployment
func (d *deployment) UpdateDeployment(namespace, content string) (err error) {
	var deploy = &appsv1.Deployment{}
	err = json.Unmarshal([]byte(content), deploy)
	if err != nil {
		logger.Error(errors.New("反序列化失败, " + err.Error()))
		return errors.New("反序列化失败, " + err.Error())
	}
	_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Update(context.TODO(),
		deploy, metav1.UpdateOptions{})
	if err != nil {
		logger.Error(errors.New("更新Deployment失败, " + err.Error()))
		return errors.New("更新Deployment失败, " + err.Error())
	}
	return nil
}

// 获取每个namespace的deployment数量
func (d *deployment) GetDeployNumPerNp() (deploysNps []*DeploysNp, err error) {
	namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(),
		metav1.ListOptions{})
	if err != nil {
		return nil, err
	}
	for _, namespace := range namespaceList.Items {
		deploymentList, err :=
			K8s.ClientSet.AppsV1().Deployments(namespace.Name).List(context.TODO(),
				metav1.ListOptions{})
		if err != nil {
			return nil, err
		}
		deploysNp := &DeploysNp{
			Namespace: namespace.Name,
			DeployNum: len(deploymentList.Items),
		}
		deploysNps = append(deploysNps, deploysNp)
	}
	return deploysNps, nil
}

// toCells方法用于将pod类型转成DataCell类型
func (*deployment) toCells(std []appsv1.Deployment) []DataCell {
	cells := make([]DataCell, len(std))
	for i := range std {
		//std[i]原生pod类型 -> podCell -> DataCell
		cells[i] = deploymentCell(std[i])
	}
	return cells
}

// fromCells方法用于将DataCell类型转成pod类型
func (*deployment) fromCells(cells []DataCell) []appsv1.Deployment {
	deployments := make([]appsv1.Deployment, len(cells))
	for i := range cells {
		//cells[i] DataCell接口类型 -> podCell -> 原生pod类型
		deployments[i] = appsv1.Deployment(cells[i].(deploymentCell))
	}
	return deployments
}
